﻿

#region Using Statements

// System References
using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Threading;

// XNA References
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

// Internal References
using Freeform.Framework;

#endregion


namespace Freeform.Framework.Geometry.Collision
{
    /// <summary>
    /// Collision properties define whether or not an entity collides with others, which entities they collide with and what method
    /// of collision detection will be used.
    /// </summary>
    public class CollisionProperties
    {

        #region Events

        /// <summary>
        /// This event is fired when the entity that owns these collision properties collides with another entity.
        /// </summary>
        public event CollisionOccuredEventHandler CollisionOccured;
        public delegate void CollisionOccuredEventHandler(Entity thisEntity, Entity otherEnt);

        #endregion


        #region Members

        /// <summary>
        /// The collision ID determines how other entities interact with this one according to the other entity's collision mask.
        /// </summary>
        public virtual Byte CollisionID
        {
            get { return _CollisionID; }
            set
            {
                if (value > 63) throw new Exception("A collision ID must be between 0 and 63 (inclusively.)");
                _CollisionID = value;
            }
        }
        private Byte _CollisionID = 0;


        /// <summary>
        /// The collision mask is a 64-bit unsigned integer. Each bit represents a boolean value as to whether or not this
        /// entity collides with an entity of a given CollisionID.
        /// Example: A collision mask of: 01001100 00000000 ..... = 50 would mean that the entity collides with other entities
        /// with CollisionID of 1, 4 or 5.
        /// Use SetCollisionMask() to set whether or not this entity collides with an entity of a given CollisionID.
        /// </summary>
        public UInt64 CollisionMask { get; private set; }


        /// <summary>
        /// The collision type variable determines the method used for detecting collisions for this entity.
        /// </summary>
        public CollisionType CollisionType { get; set; }


        /// <summary>
        /// Gets or sets the collision geometry used for collision detection.
        /// </summary>
        public CollisionGeometry Geometry { get; internal set; }


        /// <summary>
        /// Gets the entity that these collision properties refer to.
        /// </summary>
        public Entity Entity { get; private set; }


        /// <summary>
        /// Gets or sets the collision handler that is invoked when a collision is detected.
        /// </summary>
        public CollisionHandler Handler { get; set; }

        #endregion


        #region Init

        /// <summary>
        /// Creates a new set of collision properties.
        /// </summary>
        /// <param name="entity">The entity to which the properties pertain.</param>
        public CollisionProperties(Entity entity)
        {
            Entity = entity;
            CollisionType = CollisionType.None;
        }

        #endregion


        #region Utility

        /// <summary>
        /// Specifies that this entity will collide with another entity with the specified collisionID if value is set to true.
        /// No such collision will occur if the value is set to false.
        /// </summary>
        /// <param name="id">The CollisionID.</param>
        /// <param name="value">True to enforce collisions, false otherwise.</param>
        public virtual void SetCollisionMask(Byte id, Boolean value)
        {
            if (id > 63)
                throw new Exception("A collision ID must be between 0 and 63 (inclusively.)");

            UInt64 mask = ((UInt64)1 << id);

            if (value)
                CollisionMask |= mask;
            else
            {
                mask = ~mask;
                CollisionMask &= mask;
            }
        }


        /// <summary>
        /// Checks whether or not the two collision properties collide.
        /// </summary>
        /// <param name="other">The collision property of the other entity.</param>
        /// <returns>True if and only if a collision occurs.</returns>
        public virtual Boolean CheckCollision(CollisionProperties other)
        {
            // Check early breaks.
            if (Entity.IsDisposed || other.Entity.IsDisposed) return false;
            if (!Entity.IsEnabled || !other.Entity.IsEnabled) return false;

            // Check if collision masks don't match up.
            if ((CollisionMask >> other.CollisionID) % 2 == 0) return false;

            // Check for geometric collision.
            return Geometry.CheckCollision(other.Geometry);
        }


        /// <summary>
        /// Causes the collision handler to be invoked for this entity.
        /// </summary>
        /// <param name="otherEntity">The entity with which we have collided.</param>
        /// <param name="mutual">If true, this will cause the other entity's collision handler to be invoked also.</param>
        public virtual void FireCollision(Entity otherEntity, Boolean mutual) 
        {
            // Allow handler to handle collision.
            if (Handler != null)
                Handler.HandleCollision(otherEntity);

            // Fire collision event.
            if (CollisionOccured != null)
                CollisionOccured(Entity, otherEntity);

            // Fire mutual collision.
            if (mutual)
                FireCollision(Entity, false);
        }

        #endregion

    }
}
